#include <vector>
#include <sstream>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>
#include "UnsafeReceiver.h"
#include "ReceiverError.h"
#include "../../comm/CommError.h"
#include "../../system/Queues.h"
#include "../../system/Observers.h"
#include "../../config/Config.h"
#include "../../utils/Time.h"

#define foreach BOOST_FOREACH

using namespace kc_sf;

namespace kc_sf
{
	const std::string Wsp = "\t \r\n";
}

// ---------------------------------------------------------------- //

UnsafeReceiver::UnsafeReceiver( const Connection &connection )
: Receiver( connection )
{
}

// ---------------------------------------------------------------- //

CommRequestHandler *UnsafeReceiver::getInstance( const Connection &connection ) const
{
	return new UnsafeReceiver( connection );
}

// ---------------------------------------------------------------- //

void UnsafeReceiver::operator ()()
{
	if( r_comm == NULL )
	{
		r_comm = new TCPCommon( r_connection.cSocketFd, MAX_LINE_LENGTH );
		r_logger << info << receiver << cidIn( r_connection.cCid ) << "Nawiązano nieszyfrowaną sesję" << flush;
	}

	Receiver::operator ()();
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processHelo( const Command &cmd )
{
    Reply reply = StandardReplies.at( Ok_Ready );
	return processBaseHelo( cmd, reply );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processEhlo( const Command &cmd )
{
    std::vector<std::string> esmtp;
    esmtp.push_back( "8BITMIME" );
    esmtp.push_back( "SIZE " + boost::lexical_cast<std::string>( Config::getInstance().cMaxMessageSize ) );
    esmtp.push_back( "STARTTLS" );
    //esmtp.push_back( "PIPELINING" );
    //esmtp.push_back( "CHUNKING" );
    esmtp.push_back( "AUTH PLAIN LOGIN CRAM-MD5" );

    Reply reply = StandardReplies.at( Ok_Ready );
	foreach( std::string extension, esmtp )
	{
		reply.TextLines.push_back (extension);
	}

	return processBaseHelo( cmd, reply );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processBaseHelo( const Command &cmd, Reply &reply )
{
    if( !isAllowedInState( cmd.Type ) )
        return sendReply( StandardReplies.at( No_BadCommandsSeq ) );

    // wyluskaj z komendy nazwe klienta
    std::vector<std::string> words;
    words = boost::split( words, cmd.Data, boost::is_any_of( Wsp ) );
    words.erase( std::remove( words.begin(), words.end(), "" ), words.end() );

    if( words.empty() )
        return sendReply(  ( StandardReplies.at( No_SyntaxError ) ) );

    r_cl_name = words[0];
    r_logger << info << receiver << cidIn( r_connection.cCid ) << "Klient przedstawił się jako " << r_cl_name << flush;

    r_state = HelloState;

	reply.TextLines[0] += " greets " + r_cl_name;
    return sendReply( reply );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processAuthPlain( const Command &cmd )
{
	return sendReply(  ( StandardReplies.at( No_NotImplemented ) ) );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processAuthLogin( const Command &cmd )
{
	return sendReply(  ( StandardReplies.at( No_NotImplemented ) ) );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processStartTLS()
{
	Reply reply = StandardReplies.at( No_NotImplemented );
	return sendReply( reply );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processMailFrom( const Command &cmd )
{
    if( !isAllowedInState( cmd.Type ) )
        return sendReply(  ( StandardReplies.at( No_BadCommandsSeq ) ) );

    // MAIL FROM:<reverse-path> [SP <mail-parameters> ] <CRLF>
    std::vector<std::string> words;
    words = boost::split( words, cmd.Data, boost::is_any_of( Wsp ) );

    if( words.empty() )
        return sendReply(  ( StandardReplies.at( No_SyntaxError ) ) );

    // pierwsze slowo to <reverse-path>
    Address address;
    if( !stripSourceRoute( words[ 0 ], address ) )
        return sendReply( ( StandardReplies.at( No_SyntaxError ) ) );

    // BODY=8BITMIME
    bool _8bitmime = false;
    foreach( std::string param, words )
    {
    	if( boost::istarts_with( param, "body" ) )
    	{
    		_8bitmime = true;
    		break;
    	}
    }

    r_msg = RawMessage ();
    r_msg.rmMailFrom = address;
    r_state = MailState;

    Reply reply = StandardReplies.at( Ok_Ok );
    reply.TextLines[ 0 ] = "<" + address.toString() + ">";
    reply.TextLines[ 0 ] += _8bitmime ? " Sender and 8BITMIME ok"
    		: "Sender ok";

    return sendReply( reply );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processRcpt( const Command &cmd )
{
    if( !isAllowedInState( cmd.Type ) )
        return sendReply( ( StandardReplies.at( No_BadCommandsSeq ) ) );

    // parametry sa pomijane
    // nie zna lokalnych klientow - akceptuje wszystkich - open relay
    // wyluskaj z komendy adres nadawcy
    // RCPT TO:<forward-path> [ SP <rcpt-parameters> ] <CRLF>
    // forward-path = route_adress,
    // route_adress:mailbox | Postmaster@Domain | Postmaster

    std::vector<std::string> words;
    words = boost::split( words, cmd.Data, boost::is_any_of( Wsp ) );

    if( words.empty() )
        return sendReply(  ( StandardReplies.at( No_SyntaxError ) ) );

    // pierwsze slowo to <forward-path>
    Address address;
    if( !stripSourceRoute( words[ 0 ], address ) )
        return sendReply( ( StandardReplies.at( No_SyntaxError ) ) );

    // uaktualnij nadchodzaca wiadomosc
    r_msg.rmRcptsTo.push_back(address);
    r_state = RcptState;

    Reply reply = StandardReplies.at( Ok_Ok );
    reply.TextLines[0] = "<" + address.toString() + "> ... Recipient ok";

    return sendReply( reply );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processData()
{
    if( !isAllowedInState( Data ) )
        return sendReply(  ( StandardReplies.at( No_BadCommandsSeq ) ) );

    CommResult result = sendReply(  ( StandardReplies.at( More_StartMail ) ) );
    if( result != Success )
        return result;

    // [ mail data ] CRLF . CRLF

    std::string mail_data;	// tresc wiadomosci
	if( ( result = recvData( mail_data, EOM ) ) != Success )
		return result;

//    std::string last_line;       	// to, co odebrano w ostatniej iteracji
//	std::string previous_line; // to, co odebrano w przedostatniej iteracji
//    while( !isEndOfMsg( previous_line ) )
//    {
//    	previous_line = last_line;
//    	result = r_comm->recvData(last_line, EOM, DEF_RECEIVER_TIMEOUT_S );
//    	if( result != Success )
//    	{
//            r_logger << error << receiver << "Nie można odebrać wiadomości od klienta " << r_cl_name << flush;
//            return result;
//        }
//
//        // aktualizacja tresci wiadomosci
//        mail_data += last_line;
//        previous_line += last_line;
//    }

    // usuwanie kropki
    size_t dot = mail_data.find_last_of('.');
    if( dot != std::string::npos )
        mail_data.erase( dot, mail_data.size() - dot );

    r_state = DataState;

    r_msg.rmCameDate = now;
    r_msg.rmData = mail_data;
	r_msg.Id = MsgsObserver.add() - 1;
    InputMessageQueue.push( r_msg );
	InputMessageQueueObserver.add();

    r_logger << info << receiver << mid( r_msg.Id ) << "Odebrano nową wiadomość. Wielkość: "
             << mail_data.size() / 1000 << " " << mail_data.size() % 1000 << "B" << flush;
    r_logger << info << receiver << mid( r_msg.Id ) << "Nowa wiadomość w kolejce wiadomości przychodzących" << flush;

    Reply reply = StandardReplies.at( Ok_Ok );
    reply.TextLines = std::vector<std::string>( 1, "Message OK" );
    return sendReply( reply );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processBdat( const Command & cmd )
{
	// podziel odpowiedz na tokeny
	std::vector<std::string> words;
	words = boost::split( words, cmd.Data, boost::is_any_of( " " ) );

    if( words.empty() )
        return sendReply(  ( StandardReplies.at( No_SyntaxError ) ) );

	// ile B?
    unsigned long bytes;
    try
    {
    	bytes = boost::lexical_cast<unsigned long>( words[0] );
    }
    catch( boost::bad_lexical_cast & )
    {
        return sendReply(  ( StandardReplies.at( No_SyntaxError ) ) );
    }

	// odbierz dane
    std::string tmp;
	CommResult result = r_comm->recvData(tmp, bytes, DEF_RECEIVER_TIMEOUT_S );
	if( result != Success )
	{
		r_logger << error << receiver << "Nie można odebrać wiadomości od klienta " << r_cl_name << flush;
		return result;
	}

	r_state = BdatState;

	// czy to ostatnia porcja danych?
	bool last = words.size() > 1;
	if( last )
	{
		if( boost::iequals( words[1], "last" ) )
	        return sendReply(  ( StandardReplies.at( No_SyntaxError ) ) );

		if( !isEndOfMsg( tmp ) )
	        return sendReply(  ( StandardReplies.at( No_SyntaxError ) ) );

		// usuwanie kropki
		size_t dot = tmp.find_last_of('.');
		if( dot != std::string::npos )
			tmp.erase( dot, tmp.size() - dot );
	}

	// odpowiedz
    if( !isAllowedInState( Bdat ) )
        return sendReply(  ( StandardReplies.at( No_BadCommandsSeq ) ) );

	r_msg.rmData += tmp;
	if( last )
	{
	    r_state = DataState;

	    r_msg.rmCameDate = now;
		r_msg.Id = MsgsObserver.add() - 1;
		InputMessageQueue.push( r_msg );
		InputMessageQueueObserver.add();

		r_logger << info << receiver << mid( r_msg.Id) << "Odebrano nową wiadomość. Wielkość: "
				 << r_msg.rmData.size() / 1000 << " " << r_msg.rmData.size() % 1000 << "B" << flush;
	    r_logger << info << receiver << mid( r_msg.Id ) << "Nowa wiadomość w kolejce wiadomości przychodzących" << flush;
	}

	Reply reply = StandardReplies.at( Ok_Ok );
	std::stringstream msg_ok_stream;
	msg_ok_stream << "Message OK, " << bytes << " octets received";
	reply.TextLines = std::vector <std::string>( 1, msg_ok_stream.str() );
    return sendReply( reply );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processNoop()
{
    if( !isAllowedInState( Noop ) )
        return sendReply( StandardReplies.at( No_BadCommandsSeq ) );

    return sendReply( StandardReplies.at( Ok_Ok ) );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processQuit()
{
    if( !isAllowedInState( Quit ) )
        return sendReply( StandardReplies.at( No_BadCommandsSeq ) );

    r_state = InitState;
    return sendReply( StandardReplies.at( Ok_Closing ) );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processRset()
{
    if( !isAllowedInState( Rset ) )
        return sendReply( StandardReplies.at( No_BadCommandsSeq ) );

    r_msg = RawMessage();
    return sendReply( StandardReplies.at( Ok_Ok ) );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processUnimplemented( const Command &cmd )
{
	return sendReply( StandardReplies.at( No_NotImplemented ) );
}

// ---------------------------------------------------------------- //

CommResult UnsafeReceiver::processUndefined( const Command &cmd )
{
	 return sendReply( StandardReplies.at( No_Unrecognized ) );
}

// ---------------------------------------------------------------- //

bool UnsafeReceiver::isAllowedInState( int cmd_type )
{
    bool success = false;

    switch( r_state )
    {
        case InitState:
        case ConnectedState:
        case HelloState:
        case DataState:
            if( cmd_type != UndefinedCommand && cmd_type != Rcpt && cmd_type != Data
            	&& cmd_type != Bdat )
                success = true;
            break;
        case BdatState:
            if( cmd_type != UndefinedCommand && cmd_type != Rcpt && cmd_type != Data)
                success = true;
            break;
        case MailState:
            if( cmd_type == Rcpt || cmd_type == Rset || cmd_type == Noop
                || cmd_type == Help || cmd_type == Quit )
                success = true;
            break;
        case RcptState:
            if( cmd_type == Data || cmd_type == Bdat|| cmd_type == Rcpt || cmd_type == Rset
                || cmd_type == Noop || cmd_type == Help || cmd_type == Quit )
                success = true;
            break;
        default:
            break;
    };

    return success;
}

// ---------------------------------------------------------------- //

bool UnsafeReceiver::stripSourceRoute(const std::string &route_mailbox, Address &mailbox) const
{
    bool success = false;

    // reverse-path = route_adress,route_adress:mailbox | null
    if( !route_mailbox.empty()
    	&& route_mailbox[ 0 ] == '<'
    	&& route_mailbox[ route_mailbox.size() - 1 ] == '>' )
    {
    	std::vector <std::string> words;
    	std::string route_mailbox_nobr = route_mailbox.substr( 1, route_mailbox.size() - 2 );
    	words = boost::split( words, route_mailbox_nobr, boost::is_any_of( ",:" ) );

        if( words.empty() )  // null reverse-path
        {
            mailbox = Address();
            success = true;
        }
//        else if( boost::iequals( words[ words.size() -1 ], "postmaster" ) )   // postmaster
//        {
//            mailbox = Address( "postmaster", "" );
//            success = true;
//        }
        else if( !boost::iequals( words[ words.size() -1 ], "postmaster" ) )   // poprawny adres
		{
        	std::string address = words[ words.size() -1 ];
        	size_t at = address.find ('@');
        	if( at != std::string::npos )
        	{
				mailbox = Address(
						address.substr( 0, at ),
						address.substr( at + 1, address.size() - at - 1 ) );
	            success = true;
        	}
		}
    }

    return success;
}

// ---------------------------------------------------------------- //

bool UnsafeReceiver::isEndOfMsg( const std::string &text ) const
{
	bool success = false;
	bool crlf = false, dot = false;
	int j = 10;
	for (int i = text.size(); i > -1 && j > -1; --i, --j)
	{
		if (text[i] == '\r' || text[i] == '\n')
		{
			if (crlf && dot)
			{
				success = true;
				break;
			}
			crlf = true;
		}
		else if (text[i] == '.')
		{
			dot = true;
		}
		else
		{
			crlf = dot = false;
		}
	}

    return success;
}
